#ifndef __CPU_H__
#define __CPU_H__

#include <thread>
#include <memory>
#include <iostream>
#include "sync-queue.hpp"
#include "monitor.hpp"
#include "scheduler.h"
#include "semaphores.h"

struct Interupt
{
    long long arg1;
    long long arg2;
    long long arg3;
    long long arg4;

    enum Type
    {
        SHUT_DOWN,
        PAGE_FAULT,
        PAGE_READY,
        IO_ISSUE,
        IO_READY,
        TIME_OUT,
    } type;
};

template <class SCHEDULER_T>
class CPU
{
public:
    std::thread thread_handle;
    SyncQueue<Interupt> interupt_queue;
    SyncQueue<Interupt> *global_interupt_queue = nullptr;

    void run(std::shared_ptr<Monitor<SCHEDULER_T>>);

    int get_id() { return id; }
    void set_id(int new_id) { id = new_id; }

    int get_pid() { return pid; }
    void set_pid(int new_pid) { pid = new_pid; }

private:
    int id;
    int pid = -1;
};

template <class SCHEDULER_T>
void CPU<SCHEDULER_T>::run(std::shared_ptr<Monitor<SCHEDULER_T>> scheduler_ptr)
{
    thread_handle = std::thread([this, &scheduler_ptr]()
                                {
        while(1)
        {
            // if no task running, get a task from scheduler
            if(this->get_pid() == -1)
            {
                auto scheduler_helper = scheduler_ptr->get_access();
                int pid = scheduler_helper->get_task();
                if(pid != -1)
                {
                    this->set_pid(pid);
                    scheduler_helper->set_run(pid);
                }
            }

            // run command from address pc
            if(this->get_pid() != -1)
            {
                int pid = get_pid();

                // Cannot fold operator-> for shared_ptr. Why?
                auto PCB_ptr = scheduler_ptr->get_access()->get_PCB(pid);
                int pc = PCB_ptr->pc;
                PCB_ptr->pc = pc+1;

                if(pc >= PCB_ptr->commands.size() || pc < 0)
                {
                    scheduler_ptr->get_access()->exit(pid);
                    goto PROCESS_INTERRUPT;
                }
                auto command = PCB_ptr->commands[pc];
                
                switch (command.type)
                {
                case Command::print:
                    std::cout << command.sarg;
                    break;

                case Command::jump:
                    PCB_ptr->pc+=command.arg2;
                    break;

                case Command::wait:
                    if(command.arg1 < SEMAPHORE_COUNT && command.arg1 > 0)
                    {
                        int ret = g_semaphore_array[command.arg1].wait(PCB_ptr->pid);
                        if(ret == -1)
                        {
                            scheduler_ptr->get_access()->set_wait(PCB_ptr->pid);
                            this->pid = -1;
                        }    
                    }
                    break;

                case Command::signal:
                    if(command.arg1 < SEMAPHORE_COUNT && command.arg1 > 0)
                    {
                        int ret = g_semaphore_array[command.arg1].release();
                        if(ret != -1)
                            scheduler_ptr->get_access()->set_ready(ret);
                    }
                    break;

                case Command::sleep:
                    scheduler_ptr->get_access()->set_ready(pid);
                    set_pid(-1);

                default:
                    // not Implemented
                    break;
                }
            }

            // process interrput

PROCESS_INTERRUPT:
            auto interrupt = this->interupt_queue.try_pop();
            if(!interrupt && this->global_interupt_queue)
            {
                interrupt = this->global_interupt_queue->try_pop();
            }
            if(!interrupt)
            {
                continue;
            }

            switch (interrupt->type)
            {
                case Interupt::Type::SHUT_DOWN:
                    return ;
                    break;

                case Interupt::Type::IO_ISSUE:
                    break;

                case Interupt::Type::IO_READY:
                    break;

                case Interupt::Type::PAGE_FAULT:
                    break;

                case Interupt::Type::PAGE_READY:
                    break;

                default:
                    break;
            }
        } });
}

#endif